/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "TransactionDownloader.h"

#include "Utilities.h"
#include "TransactionDatabase.h"

namespace QSThink
{
	/*
	void TransactionDownloadThread::run()
	{

	//start download
	while(_daysLeftCount>0){
	int requestCnt=_daysRequesting.size();
	while(_parallelCount<PARALLEL_MAX&&
	_daysLeftCount>requestCnt){

	getDayTransaction(_symbol,_daysLeft[_dayIndex]);
	moveDayIndex();
	_parallelCount++;
	requestCnt=_daysRequesting.size();
	}


	//enter event loop unit exit
	exec();
	this->msleep(100);

	}

	//this->deleteLater();

	}

	TransactionDownloadThread::TransactionDownloadThread( int symbol,QDate startDate,QDate endDate )
	: _parallelCount(0)
	{
	_symbol=symbol;

	//current only one thread
	_dayStart=Utilities::getDaysFrom1900(startDate);
	_dayEnd=Utilities::getDaysFrom1900(endDate);//our program not include the last day

	_daysLeftCount=_dayEnd-_dayStart+1;//our day end 
	_daysLeft=new int[_daysLeftCount];
	for(int i=0;i<_daysLeftCount;i++){
	_daysLeft[i]=i+_dayStart;
	}

	_dayIndex=_daysLeftCount-1;//point to last for efficiency

	_netAccessMgr=new QNetworkAccessManager(this);
	connect(_netAccessMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));


	}

	bool TransactionDownloadThread::replyFinished(QNetworkReply* reply )
	{

	int day=-1;
	int size=_daysRequesting.size();

	std::vector<int>::iterator it1=_daysRequesting.begin();
	std::vector<QNetworkReply*>::iterator it2=_replysRequesting.begin();
	for(int i=0;i<size;++i){
	if(_replysRequesting[i]==reply){
	day=_daysRequesting[i];
	//
	_daysRequesting.erase(it1);
	_replysRequesting.erase(it2);
	break;
	}

	++it1;
	++it2;
	}

	if(day==-1){
	//shouldn't happen 
	assert(false);
	return false;
	}





	QByteArray bytes=reply->readAll();

	saveBytes(_symbol,day,bytes);



	removeDay(day);//represent this day's data is downloaded


	_parallelCount--;//received 

	exit(0);

	return true;
	}

	void TransactionDownloadThread::getDayTransaction(int symbol,int day)
	{
	QString url=Utilities::buildTransQueryUrl(symbol,day);


	QNetworkRequest request;
	request.setUrl(QUrl(url));

	QNetworkReply* reply=_netAccessMgr->get(request);

	connect(reply, SIGNAL(readyRead()), this, SLOT(OnReadyRead()));

	connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
	this, SLOT(slotError(QNetworkReply::NetworkError)));


	//add to vector
	_daysRequesting.push_back(day);
	_replysRequesting.push_back(reply);


	}

	TransactionDownloadThread::~TransactionDownloadThread()
	{
	if(_netAccessMgr){
	delete _netAccessMgr;
	}
	}

	bool TransactionDownloadThread::OnReadyRead()
	{
	return true;
	}

	bool TransactionDownloadThread::slotError( QNetworkReply::NetworkError error )
	{
	return true;
	}

	void TransactionDownloadThread::removeDay( int day )
	{
	int index=-1;
	for(int i=_daysLeftCount-1;i>=0;--i){//recent first download
	if(_daysLeft[i]==day){
	index=i;
	}
	}


	if(index==-1){
	assert(false);//shouldn't be
	}

	//left move copy
	for(int i=index;i<_daysLeftCount-1;++i){
	_daysLeft[i]=_daysLeft[i+1];
	}


	_daysLeftCount--;
	}

	void TransactionDownloadThread::moveDayIndex()
	{
	_dayIndex--;
	if(_dayIndex<0){
	_dayIndex=_daysLeftCount-1;
	}

	}

	void TransactionDownloadThread::saveBytes( int symbol, int day, QByteArray bytes )
	{
	QString date=Utilities::getDateStrByDay(day);
	QString fileName=QString::number(symbol);
	fileName+="_";
	fileName+=date;
	fileName+=".xls";

	QFile file(fileName);
	if(file.open(QFile::WriteOnly)){
	file.write(bytes);
	file.close();
	}

	}

	*/

	TransactionDownloadTask::TransactionDownloadTask( QString symbol )
		: _isDownloading(false)
	{
		_symbol=symbol;

		_dayStart=Utilities::getDaysFrom1900(QDate(2100,1,1));//
		_dayEnd=0;
		_started=false;


		QString fileName=_symbol+".transdown";

		QFile file(fileName);
		if (!file.open(QFile::ReadOnly)){
			return;
		}


		_started=true;
		QByteArray bytes=file.readAll();
		char* data=bytes.data();
		int index=0;

		_dayStart=*(int*)(data+index);
		index+=sizeof(_dayStart);
		_dayEnd=*(int*)(data+index);
		index+=sizeof(_dayEnd);

		int cntDown=*(int*)(data+index);
		index+=sizeof(cntDown);

		int cntLeft=*(int*)(data+index);
		index+=sizeof(cntLeft);

		for(int i=0;i<cntDown;++i){
			int day=*(int*)(data+index);
			index+=sizeof(day);
			Transaction1Day* trans=TransactionDatabase::getSingleton().getDayTransaction(_symbol,day);
			_dayTransactionsDownload.insert(DayTransactionMap::value_type(day,trans));
			
		}

		for(int i=0;i<cntLeft;++i){//first down then left
			int day=*(int*)(data+index);
			index+=sizeof(day);
			//if already in down ignore
			DayTransactionMap::iterator it=_dayTransactionsDownload.find(day);//
			if(it!=_dayTransactionsDownload.end()){
				continue;

			}

			Transaction1Day* trans=TransactionDatabase::getSingleton().getDayTransaction(_symbol,day);
			_dayTransactionsLeft.insert(DayTransactionMap::value_type(day,trans));
		}


		checkDownloaded();

	}

	void TransactionDownloadTask::persit()
	{
		QString fileName=_symbol+".transdown";

		QFile file(fileName);
		if (!file.open(QFile::WriteOnly)){
			return;
		}


		int cntLeft=_dayTransactionsLeft.size();
		int cntDown=_dayTransactionsDownload.size();


		QByteArray bytes;
		bytes.append((char*)&_dayStart,sizeof(_dayStart));
		bytes.append((char*)&_dayEnd,sizeof(_dayEnd));

		
		bytes.append((char*)&cntDown,sizeof(cntDown));
		bytes.append((char*)&cntLeft,sizeof(cntLeft));

		

		DayTransactionMap::iterator itDown=_dayTransactionsDownload.begin();
		DayTransactionMap::iterator itEndDown=_dayTransactionsDownload.end();

		for(;itDown!=itEndDown;++itDown){
			int day=itDown->first;
			bytes.append((char*)&day,sizeof(day));
		}

		DayTransactionMap::iterator itLeft=_dayTransactionsLeft.begin();
		DayTransactionMap::iterator itEndLeft=_dayTransactionsLeft.end();

		for(;itLeft!=itEndLeft;++itLeft){
			int day=itLeft->first;
			bytes.append((char*)&day,sizeof(day));
		}



		file.write(bytes);
	}


	void TransactionDownloadTask::start()
	{
		if(_isDownloading){
			return;
		}

		_isDownloading=true;

		int symbol=_symbol.toInt();
		int cnt=_dayTransactionsLeft.size();
		DayTransactionMap::iterator it=_dayTransactionsLeft.begin();
		DayTransactionMap::iterator itEnd=_dayTransactionsLeft.end();

		
		for(;it!=itEnd;++it){
			int day=it->first;
			QString url=Utilities::buildTransQueryUrl(symbol,day);
			QNetworkReply* reply=NetworkSystem::getSingleton().httpGet(url);
			_replies.insert(NetReplyMap::value_type(reply,day));

		}

	}

	void TransactionDownloadTask::setStartEndDate(QDate dateStart,QDate dateEnd )
	{
		//only smaller start and bigger end accept 
		int dayStart=Utilities::getDaysFrom1900(dateStart);
		if(dayStart<_dayStart){
			_dayStart=dayStart;
		}

		int dayEnd=Utilities::getDaysFrom1900(dateEnd);
		if(dayEnd>_dayEnd){
			_dayEnd=dayEnd;
		}

		assert(dateEnd>=dateStart);
		addDays(_dayStart,_dayEnd);

		persit();

	}

	void TransactionDownloadTask::addDays( int dayStart, int dayEnd )
	{
		int cnt=dayEnd-dayStart+1;
		assert(cnt>0);

		QDate dateStart=Utilities::getDateByDay(dayStart);
		int startDayOfWeek=dateStart.dayOfWeek();//1 to 7
		int dayOfWeek=startDayOfWeek;//0 to 6( 0 means 7)
		int day=dayStart;

		for(int i=0;i<cnt;++i){
			dayOfWeek=(startDayOfWeek+i)%7;//0 to 6 0 means 7
			day=dayStart+i;//dayend=daystart+dayleft-1

			if(dayOfWeek>=1&&dayOfWeek<=5){
				//already downloaded
				DayTransactionMap::iterator it=_dayTransactionsDownload.find(day);//
				if(it!=_dayTransactionsDownload.end()){
					continue;
					
				}


				Transaction1Day* trans=TransactionDatabase::getSingleton().getDayTransaction(_symbol,day);
				_dayTransactionsLeft.insert(DayTransactionMap::value_type(day,trans));
			}

			
		}

	}

	void TransactionDownloadTask::OnNetworkReply( QNetworkReply* reply )
	{
		NetReplyMap::iterator it=_replies.find(reply);
		if(it==_replies.end()){
			//not find
			return;//but shouldn't happen
		}
		int day=it->second;
		

		DayTransactionMap::iterator it0=_dayTransactionsLeft.find(day);
		if(it0==_dayTransactionsLeft.end()){
			//not find
			return;//but shouldn't happen
		}

		Transaction1Day* trans=it0->second;
		QByteArray bytes=reply->readAll();

		if(!trans->fillData(bytes)){
			return;
		}


		_replies.erase(it);
		
		//_dayTransactionsDownload.find(day);
		_dayTransactionsDownload.insert(DayTransactionMap::value_type(day,trans));
		_dayTransactionsLeft.erase(it0);

		persit();

		TransactionDownloader::getSingleton().OnDayTransactionDownloaded(trans);

		if(_dayTransactionsLeft.size()==0){
			TransactionDownloader::getSingleton().OnTaskFinished(this);
		}
	}

	

	
	void TransactionDownloadTask::setStartDate( QDate date )
	{
		int dayStart=Utilities::getDaysFrom1900(date);
		if(dayStart>=_dayStart){
			return;
		}

		_dayStart=dayStart;
		addDays(_dayStart,_dayEnd);

		persit();

	}

	void TransactionDownloadTask::setEndDate( QDate date )
	{
		int dayEnd=Utilities::getDaysFrom1900(date);
		if(dayEnd>_dayEnd){
			_dayEnd=dayEnd;
		}

		addDays(_dayStart,_dayEnd);

		persit();
	}

	QDate TransactionDownloadTask::startDate()
	{
		return Utilities::getDateByDay(_dayStart);
	}

	QDate TransactionDownloadTask::endDate()
	{
		return Utilities::getDateByDay(_dayEnd);
	}

	void TransactionDownloadTask::checkDownloaded()
	{
		//keep corrupted data file in a file
	}

	
	TransactionDownloader::~TransactionDownloader()
	{
	}


	TransactionDownloader::TransactionDownloader()
	{
		//init download tasks.

		QFile file(PersitFileName);
		if (!file.open(QFile::ReadOnly)){
			return;
		}

		while (!file.atEnd()) {
			QByteArray bytes = file.readLine();
			QByteArray line=bytes.trimmed();

			if (!line.isEmpty()){//GB2312
				
				QString symbol=QTextCodec::codecForName("GB2312")->toUnicode(line);
				getTask(symbol);

			}
		}
		

	}

	

	TransactionDownloadTask* TransactionDownloader::getTask( QString symbol )
	{
		int cnt=_tasks.size();
		for(int i=0;i<cnt;++i){
			if(_tasks[i]->symbol()==symbol){
				return _tasks[i];
			}
		}


		TransactionDownloadTask* task=new TransactionDownloadTask(symbol);
		_tasks.push_back(task);
		persit();



		return task;
	}

	TransactionDownloadTask* TransactionDownloader::getTask( int index )
	{
		return _tasks[index];
	}

	const QString TransactionDownloader::PersitFileName="transdown.data";

	void TransactionDownloader::persit()
	{
		QFile file(PersitFileName);
		if(!file.open(QFile::WriteOnly)){
			return;
		}

		int cnt=_tasks.size();
		for(int i=0;i<cnt;i++){
			QString line=_tasks[i]->symbol();
			line+="\n";

			QByteArray bytes= QTextCodec::codecForName("GB2312")->fromUnicode(line);

			file.write(bytes);

		}

	}

	int TransactionDownloader::getTaskCount()
	{
		return _tasks.size();

	}

	TransactionDownloadTask* TransactionDownloader::addTask( QString symbol,QDate dateStart,QDate dateEnd )
	{
		int cnt=_tasks.size();
		for(int i=0;i<cnt;++i){
			if(_tasks[i]->symbol()==symbol){
				return _tasks[i];
			}
		}


		TransactionDownloadTask* task=new TransactionDownloadTask(symbol);
		task->setStartEndDate(dateStart,dateEnd);
		_tasks.push_back(task);
		persit();



		return task;

	}

	void TransactionDownloader::addListener( TransactionDownloadListener* listener )
	{
		_listeners.push_back(listener);

	}

	void TransactionDownloader::OnDayTransactionDownloaded(Transaction1Day* trans )
	{
		int cnt=_listeners.size();
		for (int i=0;i<cnt;++i)
		{
			_listeners[i]->OnDayTransactionDownloaded(trans);
		}
	}

	void TransactionDownloader::OnTaskFinished( TransactionDownloadTask* task )
	{
		int cnt=_listeners.size();
		for (int i=0;i<cnt;++i)
		{
			_listeners[i]->OnTaskFinished(task);
		}

	}


	TransactionDownloadListener::TransactionDownloadListener()
	{
		TransactionDownloader::getSingleton().addListener(this);
	}

}
